// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: linchuan

#include "coin2/exchange/bitmex/order_v1/parser.h"

#include <memory>
#include <set>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#include "coin2/base/conversion.h"
#include "coin2/base/log.h"
#include "coin2/base/string_util.h"
#include "coin2/exchange/util/json_util.h"

namespace coin2::exchange::bitmex::order_v1 {

namespace {

using coin::proto::AccountBalance;
using coin::proto::AccountPosition;
using coin::proto::CancelOrderResponse;
using coin::proto::FillType;
using coin::proto::ProductFillBundle;
using coin::proto::ProductOrderBundle;
using coin::proto::ProductOrderElement;
using coin::proto::SubmitOrderResponse;
using coin2::exchange::base::order::IOrderContext;
using coin2::exchange::bitmex::symbology::BitmexCurrency;
using coin2::exchange::bitmex_futures::symbology::BitmexFuturesProduct;

using JsonValue = rapidjson::GenericValue<rapidjson::UTF8<>>;

inline int64_t GetProcOrderId(const JsonValue& data) {
  int64_t proc_order_id = 0;
  if (data.HasMember("clOrdID")) {
    std::string proc_str = data["clOrdID"].GetString();
    try {
      proc_order_id = stoll(proc_str);
    } catch (std::exception& e) {
      LOG(ERROR) << e.what() << proc_str;
    }
  }
  return proc_order_id;
}

inline coin::proto::OrderSide GetOrderSide(const std::string_view& side) {
  if (side == "Buy") {
    return coin::proto::OrderSide::BUY_ORDER;
  } else if (side == "Sell") {
    return coin::proto::OrderSide::SELL_ORDER;
  } else {
    return coin::proto::OrderSide::UNKNOWN_ORDER_SIDE;
  }
}

inline void SetOrderStatus(const std::string_view& status, ProductOrderElement* order) {
  if (status == "New" || status == "PartiallyFilled") {
    order->set_is_live(true);
  } else if (status == "Canceled") {
    order->set_cancel_confirmed(true);
  } else if (status == "Filled") {
    order->set_fully_filled(true);
  } else {
    LOG(ERROR) << "[ParseOrderInfo] Unknown order status! " << status;
  }
}

inline std::string GetBitmexExchangeOrderErrorMsg(
    const rapidjson::GenericValue<rapidjson::UTF8<>>& json) {
  if (json.HasMember("error") & json["error"].HasMember("message")) {
    return json["error"]["message"].GetString();
  } else {
    return {};
  }
}

inline coin::proto::OrderErrorCode GetOrderErrorCodeFromBitmex(
    const rapidjson::GenericValue<rapidjson::UTF8<>>& json_error_msg) {
  using coin::proto::OrderErrorCode;
  auto result = coin::proto::OrderErrorCode::ORDER_UNKNOWN_ERROR;
  std::string_view error_code = json_error_msg.GetString();

  if (error_code == "0") {
    result = OrderErrorCode::ORDER_NO_ERROR;
  } else if (error_code == "Account has no [XBt]") {
    result = OrderErrorCode::SUBMIT_ORDER_INSUFFICIENT_FUND;
  } else if (error_code == "Account has insufficient balance for requested action.") {
    result = OrderErrorCode::SUBMIT_ORDER_INSUFFICIENT_FUND;
  } else if (error_code == "Invalid ordStatus") {
    result = OrderErrorCode::ORDER_NOT_FOUND;
  } else if (error_code == "Not Found") {
    result = OrderErrorCode::ORDER_NOT_FOUND;
  }

  return result;
}

bool IsBitmexErrMessage(const JsonValue& doc) {
  return doc.HasMember("error") && doc["error"].HasMember("msg");
}

bool IsBalance(const rapidjson::GenericValue<rapidjson::UTF8<>>& json) {
  if (!json.HasMember("table")) {
    return false;
  }
  const auto table = std::string_view(json["table"].GetString());
  return table == "margin";
}

bool IsPosition(const rapidjson::GenericValue<rapidjson::UTF8<>>& json) {
  if (!json.HasMember("table")) {
    return false;
  }
  const auto table = std::string_view(json["table"].GetString());
  return table == "position";
}

bool IsOrderUpdate(const rapidjson::GenericValue<rapidjson::UTF8<>>& json) {
  // "table":"order", "action":"insert", "update", "partial"
  // "table":"execution", "action":"insert", "partial"

  if (!json.HasMember("table")) {
    return false;
  }
  const auto table = std::string_view(json["table"].GetString());
  return table == "order" || table == "execution";
}

}  // namespace

void BitmexParser::ParseRestAccountBalance() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestAccountBalance] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  auto account_balance = ParseBalance(document);
  if (!account_balance.has_exchange() || !account_balance.has_market_type()) {
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  result_.set_type(MsgType::ACCOUNT_BALANCE);
  *result_.mutable_account_balance() = std::move(account_balance);
}

// NOTE: when field exchange and market_type are set, which means parsed successfully
AccountBalance BitmexParser::ParseBalance(const JsonValue& doc) {
  RawBalanceMap raw_balance_map;
  auto success = ParseRawBalance(doc, &raw_balance_map);
  if (!success) {
    return {};
  }

  AccountBalance acc_balance;
  acc_balance.set_exchange("Bitmex");
  acc_balance.set_market_type("Futures");
  for (const auto& native_currency : symcache_->GetCurrenciesNativeUpper()) {
    auto* balance = acc_balance.add_each_balance();
    if (raw_balance_map.count(native_currency) == 0) {
      balance->set_total(0);
      balance->set_hold(0);
      balance->set_available(0);
    } else {
      balance->MergeFrom(raw_balance_map.at(native_currency));
    }
    auto* currency = symcache_->GetCurrencyByNative(native_currency);
    balance->set_currency(currency->currency());
    balance->set_currency_native(currency->native_currency());
    DLOG(INFO) << "BALANCE: " << currency->native_currency() << " " << balance->total() << " "
               << balance->available();
  }
  is_account_balance_initialized_ = true;

  return acc_balance;
}

bool BitmexParser::ParseRawBalance(const JsonValue& doc, RawBalanceMap* raw_balance_map) {
  /*
  [
    {
      "account": 316063,
      "currency": "USDt",
      "riskLimit": 500000000000000,
      "prevState": "",
      "state": "",
      "action": "",
      "amount": 975000000,
      "pendingCredit": 0,
      "pendingDebit": 0,
      "confirmedDebit": 0,
      "prevRealisedPnl": 0,
      "prevUnrealisedPnl": 0,
      "grossComm": 0,
      "grossOpenCost": 0,
      "grossOpenPremium": 0,
      "grossExecCost": 0,
      "grossMarkValue": 0,
      "riskValue": 0,
      "taxableMargin": 0,
      "initMargin": 0,
      "maintMargin": 0,
      "sessionMargin": 0,
      "targetExcessMargin": 0,
      "varMargin": 0,
      "realisedPnl": 0,
      "unrealisedPnl": 0,
      "indicativeTax": 0,
      "unrealisedProfit": 0,
      "syntheticMargin": null,
      "walletBalance": 975000000,
      "marginBalance": 975000000,
      "marginBalancePcnt": 1,
      "marginLeverage": 0,
      "marginUsedPcnt": 0,
      "excessMargin": 975000000,
      "excessMarginPcnt": 1,
      "availableMargin": 975000000,
      "withdrawableMargin": 975000000,
      "grossLastValue": 0,
      "commission": null,
      "makerFeeDiscount": null,
      "takerFeeDiscount": null,
      "timestamp": "2021-11-17T04:00:00.161Z"
    },
    {
      "account": 316063,
      "currency": "XBt",
      "riskLimit": 1000000000000,
      "prevState": "",
      "state": "",
      "action": "",
      "amount": 9950000,
      "pendingCredit": 0,
      "pendingDebit": 0,
      "confirmedDebit": 0,
      "prevRealisedPnl": 0,
      "prevUnrealisedPnl": 0,
      "grossComm": 0,
      "grossOpenCost": 0,
      "grossOpenPremium": 0,
      "grossExecCost": 0,
      "grossMarkValue": 0,
      "riskValue": 0,
      "taxableMargin": 0,
      "initMargin": 0,
      "maintMargin": 0,
      "sessionMargin": 0,
      "targetExcessMargin": 0,
      "varMargin": 0,
      "realisedPnl": 0,
      "unrealisedPnl": 0,
      "indicativeTax": 0,
      "unrealisedProfit": 0,
      "syntheticMargin": null,
      "walletBalance": 9950000,
      "marginBalance": 9950000,
      "marginBalancePcnt": 1,
      "marginLeverage": 0,
      "marginUsedPcnt": 0,
      "excessMargin": 9950000,
      "excessMarginPcnt": 1,
      "availableMargin": 9950000,
      "withdrawableMargin": 9950000,
      "grossLastValue": 0,
      "commission": null,
      "makerFeeDiscount": null,
      "takerFeeDiscount": null,
      "timestamp": "2021-11-17T04:00:00.161Z"
    }
  ]
  */
  if (doc.IsObject() && IsBitmexErrMessage(doc)) {
    LOG(ERROR) << "[ParseBalance] " << JsonToString(doc);
    return false;
  }
  if (!doc.IsArray()) {
    LOG(ERROR) << "[ParseBalance] " << JsonToString(doc);
    return false;
  }

  for (const auto& ele : doc.GetArray()) {
    const auto& info = ele.GetObject();
    if (!info.HasMember("marginBalance")) {
      continue;
    }
    auto native_currency = StringToUpperCopy(info["currency"].GetString());
    auto multiplier = GetMultiplier(native_currency);
    CurrencyBalance balance;
    balance.set_currency_native(native_currency);
    balance.set_available(stod(info["availableMargin"].GetString()) / multiplier);
    balance.set_total(stod(info["marginBalance"].GetString()) / multiplier);
    balance.set_hold(balance.total() - balance.available());
    raw_balance_map->emplace(balance.currency_native(), balance);
  }

  return true;
}

void BitmexParser::ParseRestAccountPosition() {
  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[ParseRestAccountPosition] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  auto account_position = ParsePosition(doc);
  if (!account_position.has_exchange() || !account_position.has_market_type()) {
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  result_.set_type(MsgType::ACCOUNT_POSITION);
  *result_.mutable_account_position() = std::move(account_position);
}
// NOTE: when field exchange and market_type are set, which means parsed successfully
AccountPosition BitmexParser::ParsePosition(const JsonValue& doc) {
  RawPositionMap raw_position_map;
  auto success = ParseRawPosition(doc, &raw_position_map);
  if (!success) {
    return {};
  }

  AccountPosition acc_position;
  acc_position.set_exchange("Bitmex");
  acc_position.set_market_type("Futures");
  for (const auto& native_symbol : symcache_->GetProductNative()) {
    if (raw_position_map.count(native_symbol) != 0) {
      continue;
    }
    auto* position = acc_position.add_each_position();
    position->set_long_position(0);
    position->set_short_position(0);
    position->set_net_position(0);
    position->set_available_long_position(0);
    position->set_available_short_position(0);
    position->set_native_symbol(native_symbol);

    DLOG(INFO) << "POS: " << native_symbol << " " << position->net_position();
  }

  for (auto& pair : raw_position_map) {
    auto* position = acc_position.add_each_position();
    *position = std::move(pair.second);
    position->set_native_symbol(pair.first);
    DLOG(INFO) << "POS: " << pair.first << " " << position->net_position();
  }
  is_account_position_initialized_ = true;

  return acc_position;
}

bool BitmexParser::ParseRawPosition(const JsonValue& doc, RawPositionMap* raw_position_map) {
  /*
  [
    {
      "account": 62148,
      "symbol": "XBTUSD",
      "currency": "XBt",
      "underlying": "XBT",
      "quoteCurrency": "USD",
      "commission": 0.00075,
      "initMarginReq": 0.01,
      "maintMarginReq": 0.005,
      "riskLimit": 20000000000,
      "leverage": 100,
      "crossMargin": true,
      "deleveragePercentile": null,
      "rebalancedPnl": 0,
      "prevRealisedPnl": 0,
      "prevUnrealisedPnl": 0,
      "prevClosePrice": 6399.22,
      "openingTimestamp": "2020-04-01T07:00:00.000Z",
      "openingQty": 0,
      "openingCost": 0,
      "openingComm": 0,
      "openOrderBuyQty": 1,
      "openOrderBuyCost": -20000,
      "openOrderBuyPremium": 0,
      "openOrderSellQty": 0,
      "openOrderSellCost": 0,
      "openOrderSellPremium": 0,
      "execBuyQty": 0,
      "execBuyCost": 0,
      "execSellQty": 0,
      "execSellCost": 0,
      "execQty": 0,
      "execCost": 0,
      "execComm": 0,
      "currentTimestamp": "2020-04-01T07:52:30.880Z",
      "currentQty": 0,
      "currentCost": 0,
      "currentComm": 0,
      "realisedCost": 0,
      "unrealisedCost": 0,
      "grossOpenCost": 20000,
      "grossOpenPremium": 0,
      "grossExecCost": 0,
      "isOpen": false,
      "markPrice": 6334.42,
      "markValue": 0,
      "riskValue": 20000,
      "homeNotional": 0,
      "foreignNotional": 0,
      "posState": "",
      "posCost": 0,
      "posCost2": 0,
      "posCross": 0,
      "posInit": 0,
      "posComm": 0,
      "posLoss": 0,
      "posMargin": 0,
      "posMaint": 0,
      "posAllowance": 0,
      "taxableMargin": 0,
      "initMargin": 231,
      "maintMargin": 0,
      "sessionMargin": 0,
      "targetExcessMargin": 0,
      "varMargin": 0,
      "realisedGrossPnl": 0,
      "realisedTax": 0,
      "realisedPnl": 0,
      "unrealisedGrossPnl": 0,
      "longBankrupt": 0,
      "shortBankrupt": 0,
      "taxBase": 0,
      "indicativeTaxRate": null,
      "indicativeTax": 0,
      "unrealisedTax": 0,
      "unrealisedPnl": 0,
      "unrealisedPnlPcnt": 0,
      "unrealisedRoePcnt": 0,
      "simpleQty": null,
      "simpleCost": null,
      "simpleValue": null,
      "simplePnl": null,
      "simplePnlPcnt": null,
      "avgCostPrice": null,
      "avgEntryPrice": null,
      "breakEvenPrice": null,
      "marginCallPrice": null,
      "liquidationPrice": null,
      "bankruptPrice": null,
      "timestamp": "2020-04-01T07:52:30.880Z",
      "lastPrice": 6334.42,
      "lastValue": 0
    }
  ]
  */
  if (doc.IsObject() && IsBitmexErrMessage(doc)) {
    LOG(ERROR) << "[ParsePosition] " << JsonToString(doc);
    return false;
  }
  if (!doc.IsArray()) {
    LOG(ERROR) << "[ParsePosition] unexpected position format!" << JsonToString(doc);
    return false;
  }

  for (const auto& data : doc.GetArray()) {
    const auto& native_symbol = data["symbol"].GetString();
    auto& position = (*raw_position_map)[native_symbol];

    int64_t multiplier = 1e8;
    if (symcache_->has_product_native(native_symbol)) {
      auto* product = symcache_->GetProductByNative(native_symbol);
      multiplier = GetMultiplier(product->quote().currency());
    }

    auto current_qty = stod(data["currentQty"].GetString());
    if (current_qty > 0) {
      position.set_long_position(current_qty);
    } else if (current_qty < 0) {
      position.set_short_position(-current_qty);
    }
    position.set_net_position(current_qty);

    position.set_realized_pnl(stod(data["realisedPnl"].GetString()) / multiplier);
    position.set_unrealized_pnl(stod(data["unrealisedPnl"].GetString()) / multiplier);
    position.set_total_pnl(position.realized_pnl() + position.unrealized_pnl());
  }

  return true;
}

AccountBalance BitmexParser::ParseWsBalance(const JsonValue& doc) {
  /*
 {
    "table":"margin",
    "action":"update",
    "data":[
      {
        "account":"321106",
        "grossOpenCost":"212740",
        "riskValue":"425540",
        "initMargin":"2449",
        "excessMargin":"119555429",
        "availableMargin":"119555429",
        "withdrawableMargin":"119555429",
        "timestamp":"2020-06-18T02:28:00.008Z",
        "currency":"XBt"
      }]
  }
  */
  ignorable_ws_update_ = false;
  if (!is_account_balance_initialized_) {
    ignorable_ws_update_ = true;
    return {};
  }
  if (!doc.HasMember("action")) {
    LOG(ERROR) << "[ParseWsBalance] " << JsonToString(doc);
    return {};
  }

  std::string_view action = doc["action"].GetString();
  if (action == "delete") {
    AccountBalance acc_balance;
    acc_balance.set_exchange("Bitmex");
    acc_balance.set_market_type("Futures");
    for (const auto& info : doc["data"].GetArray()) {
      auto native_currency = StringToUpperCopy(info["currency"].GetString());
      if (!symcache_->has_currency_native(native_currency)) {
        continue;
      }
      auto* currency = symcache_->GetCurrencyByNative(native_currency);
      auto* currency_balance = acc_balance.add_each_balance();
      currency_balance->set_currency(currency->currency());
      currency_balance->set_currency_native(currency->native_currency());
      currency_balance->set_available(0);
      currency_balance->set_total(0);
      currency_balance->set_hold(0);
    }
    return acc_balance;
  } else if (action != "update") {
    // like partial
    ignorable_ws_update_ = true;
    return {};
  }

  if (!doc.HasMember("data") || !doc["data"].IsArray()) {
    LOG(ERROR) << "[ParseWsBalance] " << JsonToString(doc);
    return {};
  }

  AccountBalance acc_balance;
  acc_balance.set_exchange("Bitmex");
  acc_balance.set_market_type("Futures");
  for (const auto& data : doc["data"].GetArray()) {
    auto native_currency = StringToUpperCopy(data["currency"].GetString());
    auto multiplier = GetMultiplier(native_currency);
    if (!symcache_->has_currency_native(native_currency)) {
      continue;
    }
    auto* currency = symcache_->GetCurrencyByNative(native_currency);
    if (!data.HasMember("availableMargin") || !data.HasMember("marginBalance")) {
      continue;
    }
    auto* currency_balance = acc_balance.add_each_balance();
    double avail = stod(data["availableMargin"].GetString()) / multiplier;
    double total = stod(data["marginBalance"].GetString()) / multiplier;
    double hold = total - avail;
    currency_balance->set_currency(currency->currency());
    currency_balance->set_currency_native(currency->native_currency());
    currency_balance->set_available(avail);
    currency_balance->set_total(total);
    currency_balance->set_hold(hold);
  }

  return acc_balance;
}

AccountPosition BitmexParser::ParseWsPosition(const JsonValue& doc) {
  /*
  {
    "table": "position",
    "action": "update",
    "data": [
      {
        "account": 321106,
        "symbol": "XBTUSD",
        "currentTimestamp": "2020-05-15T11:01:19.340Z",
        "markPrice": 9579.44,
        "markValue": -208780,
        "riskValue": 208780,
        "homeNotional": 0.0020878,
        "maintMargin": 60083,
        "unrealisedGrossPnl": 57220,
        "unrealisedPnl": 57220,
        "unrealisedPnlPcnt": 0.2151,
        "unrealisedRoePcnt": 21.5113,
        "timestamp": "2020-05-15T11:01:19.340Z",
        "lastPrice": 9579.44,
        "lastValue": -208780,
        "currency": "XBt",
        "currentQty": 20,
        "liquidationPrice": 17
      }
    ]
  }
  */
  ignorable_ws_update_ = false;
  if (!is_account_position_initialized_) {
    ignorable_ws_update_ = true;
    return {};
  }
  if (!doc.HasMember("action")) {
    LOG(ERROR) << "[ParseWsPosition] " << JsonToString(doc);
    return {};
  }
  if (!doc.HasMember("data") || !doc["data"].IsArray()) {
    LOG(ERROR) << "[ParseWsPosition] " << JsonToString(doc);
    return {};
  }

  AccountPosition acc_position;
  acc_position.set_exchange("Bitmex");
  acc_position.set_market_type("Futures");
  const auto action = std::string_view(doc["action"].GetString());
  for (const auto& data : doc["data"].GetArray()) {
    const auto& native_symbol = data["symbol"].GetString();

    int64_t multiplier = 1e8;
    if (symcache_->has_product_native(native_symbol)) {
      auto* product = symcache_->GetProductByNative(native_symbol);
      multiplier = GetMultiplier(product->quote().currency());
    }

    auto* position = acc_position.add_each_position();
    position->set_native_symbol(native_symbol);

    if (action == "delete") {
      position->set_long_position(0);
      position->set_short_position(0);
      position->set_net_position(0);
    } else {
      auto current_qty = stod(data["currentQty"].GetString());
      if (current_qty > 0) {
        position->set_long_position(current_qty);
      } else if (current_qty < 0) {
        position->set_short_position(-current_qty);
      }
      position->set_net_position(current_qty);

      if (data.HasMember("realisedPnl")) {
        position->set_realized_pnl(stoll(data["realisedPnl"].GetString()) / multiplier);
      }
      if (data.HasMember("unrealisedPnl")) {
        position->set_unrealized_pnl(stod(data["unrealisedPnl"].GetString()) / multiplier);
      }
      position->set_total_pnl(position->realized_pnl() + position->unrealized_pnl());
    }
  }

  return acc_position;
}

ProductOrderElement BitmexParser::ParseOrderInfo(const JsonValue& data) {
  /*
  [
    {
      "orderID": "01ed79e2-3475-8a81-5819-5b41b11b6647",
      "clOrdID": "",
      "clOrdLinkID": "",
      "account": 62148,
      "symbol": "XBTUSD",
      "side": "Buy",
      "simpleOrderQty": null,
      "orderQty": 1,
      "price": 5000,
      "displayQty": null,
      "stopPx": null,
      "pegOffsetValue": null,
      "pegPriceType": "",
      "currency": "USD",
      "settlCurrency": "XBt",
      "ordType": "Limit",
      "timeInForce": "GoodTillCancel",
      "execInst": "",
      "contingencyType": "",
      "exDestination": "XBME",
      "ordStatus": "New",
      "triggered": "",
      "workingIndicator": true,
      "ordRejReason": "",
      "simpleLeavesQty": null,
      "leavesQty": 1,
      "simpleCumQty": null,
      "cumQty": 0,
      "avgPx": null,
      "multiLegReportingType": "SingleSecurity",
      "text": "Submission from www.bitmex.com",
      "transactTime": "2020-04-01T07:40:23.981Z",
      "timestamp": "2020-04-01T07:40:23.981Z"
    }
  ]
  */
  ProductOrderElement order;
  order.set_market_type(MarketType::Futures);
  order.set_exchange_type(ExchangeType::Bitmex);
  order.set_api_version("v1");
  order.set_exchange_order_id(data["orderID"].GetString());
  order.set_symbol(data["symbol"].GetString());
  order.set_price(stod(data["price"].GetString()));
  order.set_qty(stod(data["orderQty"].GetString()));
  order.set_proc_order_id(GetProcOrderId(data));
  order.set_side(GetOrderSide(data["side"].GetString()));
  order.set_timestamp(msg_->timestamp);
  SetOrderStatus(data["ordStatus"].GetString(), &order);
  return order;
}

void BitmexParser::ParseRestOrdersInfo() {
  JsonDocument data;
  if (!PrepareJsonDocument(&data)) {
    LOG(ERROR) << "[ParseRestOrdersInfo] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  if (data.IsObject() && IsBitmexErrMessage(data)) {
    LOG(ERROR) << "[ParseRestOrdersInfo] " << JsonToString(data);
    return;
  }

  if (!data.IsArray()) {
    LOG(ERROR) << "[ParseRestOrdersInfo] Unexpected order list format!" << JsonToString(data);
    return;
  }

  auto product_order_bundle = result_.mutable_order_list();
  product_order_bundle->Clear();
  bool product_order_bundle_found = false;
  for (const auto& info : data.GetArray()) {
    auto* order = product_order_bundle->add_each_order();
    auto new_order = ParseOrderInfo(info);
    order->MergeFrom(new_order);
    product_order_bundle_found = true;
  }
  if (product_order_bundle_found) {
    result_.set_type(MsgType::ORDER_STATUS);
  }
}

ProductOrderBundle BitmexParser::ParseWsOrderInfoListV2(const JsonValue& data) {
  ProductOrderBundle product_order_bundle;

  if (!(data.HasMember("data") && data["data"].IsArray()) && data.HasMember("action")) {
    LOG(ERROR) << "Unexpected ws order list format!" << JsonToString(data);
    return product_order_bundle;
  }

  std::string action = data["action"].GetString();
  // Ignore Partial
  if (action == "insert") {
    for (const auto& info : data["data"].GetArray()) {
      auto* order = product_order_bundle.add_each_order();
      auto new_order = ParseOrderInfo(info);
      order->MergeFrom(new_order);
    }
  } else if (action == "update") {
    for (const auto& info : data["data"].GetArray()) {
      if (info.HasMember("ordStatus")) {
        auto* order = product_order_bundle.add_each_order();
        order->set_market_type(MarketType::Futures);
        order->set_exchange_type(ExchangeType::Bitmex);
        order->set_api_version("v1");
        order->set_exchange_order_id(info["orderID"].GetString());
        order->set_proc_order_id(GetProcOrderId(info));
        order->set_timestamp(msg_->timestamp);
        SetOrderStatus(info["ordStatus"].GetString(), order);
      }
    }
  }
  return product_order_bundle;
}

void BitmexParser::SetProductFillBundleFromRaw(
    const JsonValue& data,
    coin::proto::ProductFillBundle* product_fill_bundle) {
  /*
  [
    {
      "execID": "5144ab88-32b6-cd66-63ae-2dc7fac2723b",
      "orderID": "cca3ec4d-653a-2fd7-8d0a-3bde1aea95cf",
      "clOrdID": "",
      "clOrdLinkID": "",
      "account": 62148,
      "symbol": "XBTZ17",
      "side": "Sell",
      "lastQty": 26,
      "lastPx": 4030,
      "underlyingLastPx": null,
      "lastMkt": "XBME",
      "lastLiquidityInd": "RemovedLiquidity",
      "simpleOrderQty": null,
      "orderQty": 300,
      "price": 4030,
      "displayQty": null,
      "stopPx": null,
      "pegOffsetValue": null,
      "pegPriceType": "",
      "currency": "USD",
      "settlCurrency": "XBt",
      "execType": "Trade",                   // other value example: "Funding"
      "ordType": "Limit",
      "timeInForce": "GoodTillCancel",
      "execInst": "",
      "contingencyType": "",
      "exDestination": "XBME",
      "ordStatus": "PartiallyFilled",
      "triggered": "",
      "workingIndicator": true,
      "ordRejReason": "",
      "simpleLeavesQty": 0.068,
      "leavesQty": 274,
      "simpleCumQty": 0.00645164,
      "cumQty": 26,
      "avgPx": 4030,
      "commission": 0.00075,
      "tradePublishIndicator": "PublishTrade",
      "multiLegReportingType": "SingleSecurity",
      "text": "Submission from www.bitmex.com",
      "trdMatchID": "092ec4c3-7ffe-99b3-544e-2f2826b89750",
      "execCost": 645164,
      "execComm": 484,
      "homeNotional": -0.00645164,
      "foreignNotional": 26,
      "transactTime": "2017-09-20T15:16:56.546Z",
      "timestamp": "2017-09-20T15:16:56.546Z"
    }
  ]
  */
  if (!(data.IsArray() && data.Size() > 0)) {
    return;
  }

  for (const auto& info : data.GetArray()) {
    if (info["lastQty"].IsString() && std::string_view(info["execType"].GetString()) == "Trade") {
      auto* fill = product_fill_bundle->add_each_fill();
      fill->set_symbol(info["symbol"].GetString());
      fill->set_fill_timestamp(msg_->timestamp);
      fill->set_exchange_order_id(info["orderID"].GetString());
      if (!std::string(info["clOrdID"].GetString()).empty()) {
        fill->set_proc_order_id(stoll(info["clOrdID"].GetString()));
      }
      fill->set_price(stod(info["lastPx"].GetString()));
      fill->set_qty(stod(info["lastQty"].GetString()));
      fill->set_fill_id(info["execID"].GetString());
      std::string_view fill_type_str = info["lastLiquidityInd"].GetString();
      if (fill_type_str == "AddedLiquidity") {
        fill->set_fill_type(FillType::MAKER_FILL_TYPE);
      } else if (fill_type_str == "RemovedLiquidity") {
        fill->set_fill_type(FillType::TAKER_FILL_TYPE);
      } else {
        fill->set_fill_type(FillType::UNKNOWN_FILL_TYPE);
      }
    }
  }
  return;
}

void BitmexParser::ParseRestFillInfoList() {
  JsonDocument data;
  if (!PrepareJsonDocument(&data)) {
    LOG(ERROR) << "[ParseRestFillInfoList] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  if (data.IsObject() && IsBitmexErrMessage(data)) {
    LOG(ERROR) << "[ParseRestFillInfoList] " << JsonToString(data);
    return;
  }
  auto product_fill_bundle = result_.mutable_fill_list();
  SetProductFillBundleFromRaw(data, product_fill_bundle);
  if (result_.fill_list().each_fill().size() > 0) {
    result_.set_type(MsgType::ORDER_FILL);
  }
}

ProductFillBundle BitmexParser::ParseWsFillInfoListV2(const JsonValue& data) {
  ProductFillBundle product_fill_bundle;
  if (data.IsObject() && IsBitmexErrMessage(data)) {
    LOG(ERROR) << "[ParseWsFillInfoList] " << JsonToString(data);
    return product_fill_bundle;
  }
  if (data.HasMember("data")) {
    SetProductFillBundleFromRaw(data["data"], &product_fill_bundle);
  }
  return product_fill_bundle;
}

void BitmexParser::ParseSubmitResponse() {
  /*
  {
    "clOrdID": "1586743799777260364",
    "execInst": "ParticipateDoNotInitiate",
    "ordType": "Limit",
    "orderQty": "1.000000",
    "price": "6731.500000",
    "side": "Sell",
    "symbol": "XBTUSD",
    "timeInForce": "GoodTillCancel"
  }
  */
  const auto order_context = msg_->oc;
  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }

  result_.set_type(MsgType::SUBMIT_RESPONSE);
  auto resp = result_.mutable_submit_response();
  resp->Clear();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_http_status(http_context->res.result_int());
  resp->set_timestamp(msg_->timestamp);

  if (http_context->status == AsioHttpContextStatus::kError) {
    LOG(ERROR) << "[BitmexSubmitOrder] AsioHttpContextStatus Error";
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NETWORK_ERROR);
    resp->set_success(false);
    return;
  }

  if (http_context->res.result_int() == 429) {
    LOG(ERROR) << "[BitmexSubmitOrder] API_LIMIT_ERROR";
    resp->set_error_code(coin::proto::OrderErrorCode::API_LIMIT_ERROR);
    resp->set_success(false);
    return;
  }

  JsonDocument json;
  if (!PrepareJsonDocument(&json)) {
    LOG(ERROR) << "[BitmexSubmitOrder] Parse Error\n" << http_context->res;
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    resp->set_success(false);
    return;
  }

  if (json.HasMember("error") && json["error"].HasMember("message")) {
    auto error_code = GetOrderErrorCodeFromBitmex(json["error"]["message"]);
    resp->set_error_code(error_code);
    resp->set_exchange_error_msg(GetBitmexExchangeOrderErrorMsg(json));
    if (error_code != coin::proto::OrderErrorCode::ORDER_NO_ERROR) {
      LOG(ERROR) << "[BitmexSubmitOrder] Error Response" << http_context->res;
    }
    resp->set_success(false);
    return;
  }
  if (!json.HasMember("orderID")) {
    LOG(ERROR) << "[BitmexSubmitOrder] exchange order id not found\n" << http_context->res;
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_UNKNOWN_ERROR);
    resp->set_success(false);
    return;
  }
  resp->set_success(true);
  resp->set_exchange_order_id(json["orderID"].GetString());
}

void BitmexParser::ParseCancelResponse() {
  /*
  [
    {
      "orderID": "cd0031fd-7f72-4ff7-e881-3a6ef2274c61",
      "clOrdID": "1586743799777260364",
      "clOrdLinkID": "",
      "account": 62148,
      "symbol": "XBTUSD",
      "side": "Sell",
      "simpleOrderQty": null,
      "orderQty": 1,
      "price": 6731.5,
      "displayQty": null,
      "stopPx": null,
      "pegOffsetValue": null,
      "pegPriceType": "",
      "currency": "USD",
      "settlCurrency": "XBt",
      "ordType": "Limit",
      "timeInForce": "GoodTillCancel",
      "execInst": "ParticipateDoNotInitiate",
      "contingencyType": "",
      "exDestination": "XBME",
      "ordStatus": "Canceled",
      "triggered": "",
      "workingIndicator": false,
      "ordRejReason": "",
      "simpleLeavesQty": null,
      "leavesQty": 0,
      "simpleCumQty": null,
      "cumQty": 0,
      "avgPx": null,
      "multiLegReportingType": "SingleSecurity",
      "text": "Canceled: Canceled via API.\nSubmitted via API.",
      "transactTime": "2020-04-13T02:10:20.885Z",
      "timestamp": "2020-04-13T02:10:23.894Z"
    }
  ]
  */
  const auto order_context = msg_->oc;
  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }

  result_.set_type(MsgType::CANCEL_RESPONSE);
  auto resp = result_.mutable_cancel_response();
  resp->Clear();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_http_status(http_context->res.result_int());
  resp->set_exchange_order_id(order_context->exchange_order_id());
  resp->set_timestamp(msg_->timestamp);

  if (http_context->status == AsioHttpContextStatus::kError) {
    LOG(ERROR) << "[BitmexCancelOrder] AsioHttpContextStatus Error";
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NETWORK_ERROR);
    resp->set_success(false);
    return;
  }

  if (http_context->res.result_int() == 429) {
    LOG(ERROR) << "[BitmexCancelOrder] API_LIMIT_ERROR";
    resp->set_error_code(coin::proto::OrderErrorCode::API_LIMIT_ERROR);
    resp->set_success(false);
    return;
  }

  JsonDocument json;
  if (!PrepareJsonDocument(&json)) {
    LOG(ERROR) << "[BitmexCancelOrder] Parse Error\n" << http_context->res;
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    resp->set_success(false);
    return;
  }

  if (json.IsArray()) {
    for (auto& data : json.GetArray()) {
      if (data.HasMember("orderID")) {
        resp->set_exchange_order_id(std::string(data["orderID"].GetString()));
        resp->set_success(true);
        if (data.HasMember("ordStatus") &&
            std::string_view(data["ordStatus"].GetString()) == "Canceled") {
          resp->set_cancel_confirmed(true);
        }
        return;
      }
    }
    resp->set_success(false);
    return;
  } else if (json.HasMember("error") && json["error"].HasMember("message")) {
    auto error_code = GetOrderErrorCodeFromBitmex(json["error"]["message"]);
    resp->set_error_code(error_code);
    resp->set_exchange_error_msg(GetBitmexExchangeOrderErrorMsg(json));
    if (error_code != coin::proto::OrderErrorCode::ORDER_NO_ERROR) {
      LOG(ERROR) << "[BitmexCancelOrder] Error Response" << http_context->res;
    }
    resp->set_success(false);
    return;
  }
}

void BitmexParser::ParseWsMessage() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseWsMessage] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (IsBalance(document)) {
    auto account_balance = ParseWsBalance(document);
    if (ignorable_ws_update_) {
      result_.set_type(MsgType::IGNORABLE);
      return;
    }
    if (!account_balance.has_exchange() || !account_balance.has_market_type()) {
      result_.set_error_code(OrderErrorCode::INVALID_JSON);
      return;
    }
    result_.set_type(MsgType::ACCOUNT_BALANCE);
    *result_.mutable_account_balance() = std::move(account_balance);
  } else if (IsPosition(document)) {
    auto account_position = ParseWsPosition(document);
    if (ignorable_ws_update_) {
      result_.set_type(MsgType::IGNORABLE);
      return;
    }
    if (!account_position.has_exchange() || !account_position.has_market_type()) {
      result_.set_error_code(OrderErrorCode::INVALID_JSON);
      return;
    }
    result_.set_type(MsgType::ACCOUNT_POSITION);
    *result_.mutable_account_position() = std::move(account_position);
  } else if (IsOrderUpdate(document)) {
    if (document["table"] == "order") {
      *result_.mutable_order_list() = ParseWsOrderInfoListV2(document);
      result_.set_type(MsgType::ORDER_STATUS);
    } else if (document["table"] == "execution") {
      *result_.mutable_fill_list() = ParseWsFillInfoListV2(document);
      result_.set_type(MsgType::ORDER_FILL);
    }
  }
}

auto BitmexParser::ParseRawMessage(const RawMessage& msg) -> const ParseResult& {
  result_.Clear();
  msg_ = &msg;
  sub_topic_id_ = static_cast<OrderSubTopicId>(msg.type);

  if (msg.type == OrderSubTopicId::WS_MESSAGE) {
    ParseWsMessage();
    return result_;
  }

  switch (msg.http_context->query_type) {
    case OrderSubTopicId::SUBMIT_RESPONSE:
      ParseSubmitResponse();
      break;
    case OrderSubTopicId::CANCEL_RESPONSE:
      ParseCancelResponse();
      break;
    case OrderSubTopicId::MULTI_CANCEL_RESPONSE:
      CHECK(false);
      break;
    case OrderSubTopicId::REST_OPEN_ORDERS:
    case OrderSubTopicId::REST_ALL_ORDERS:
      ParseRestOrdersInfo();
      break;
    case OrderSubTopicId::REST_FILL_ORDERS:
      ParseRestFillInfoList();
      break;
    case OrderSubTopicId::REST_ACCOUNT_BALANCE:
      ParseRestAccountBalance();
      break;
    case OrderSubTopicId::REST_ACCOUNT_POSITION:
      ParseRestAccountPosition();
      break;
    default: {
      LOG(ERROR) << "Parsing Error. " << msg_->DebugString();
      result_.set_error_code(OrderErrorCode::ORDER_UNKNOWN_ERROR);
      break;
    }
  }

  return result_;
}

}  // namespace coin2::exchange::bitmex::order_v1
